package org.gwt.mosaic.application.rebind;

import java.io.File;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;

import org.gwt.mosaic.xul.client.ElementInstantiator;
import org.gwt.mosaic.xul.client.ui.Element;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

public class ElementInstantiatorGenerator extends Generator {

	private String className = null;

	private String packageName = null;

	@Override
	public String generate(TreeLogger logger, GeneratorContext context,
			String typeName) throws UnableToCompleteException {

		TypeOracle typeOracle = context.getTypeOracle();

		try {

			// get classType and save instance variables
			JClassType classType = typeOracle.getType(typeName);
			packageName = classType.getPackage().getName();
			className   = classType.getSimpleSourceName() + "Wrapper";

			// Generate class source code
			generateClass(logger, context);

		} catch (Exception e) {

			// record to logger that Map generation threw an exception
			logger.log(TreeLogger.ERROR, "ElementInstantiator ERROR!!!", e);

		}

		// return the fully qualifed name of the class generated
		return packageName + "." + className; 		
	}

	/**
	 * Generate source code for new class. Class implements ElementInstantiator
	 *
	 * @param logger Logger object
	 * @param context Generator context
	 */
	private void generateClass (TreeLogger logger, GeneratorContext context) {

		// get print writer that receives the source code
		PrintWriter printWriter = null;
		printWriter = context.tryCreate(logger, packageName, className);
		// print writer if null, source code has ALREADY been generated, return
		if (printWriter == null) return;

		// init composer, set class properties, create source writer
		ClassSourceFileComposerFactory composer = null;
		composer = new ClassSourceFileComposerFactory (packageName,
				className);
		
		composer.addImplementedInterface(ElementInstantiator.class.getName());
		for (String clazz : findElementClasses()) {
			composer.addImport (clazz);
		}

		SourceWriter sourceWriter = null;
		sourceWriter = composer.createSourceWriter (context, printWriter);
		
		sourceWriter.indent();
		sourceWriter.println("public Element instantiate (String className) {");
		for (String className : findElementClasses()) {
			
			try {
				Class<?> clazz = Class.forName(className);
				Object o = clazz.newInstance();
				
				// if no exceptions are thrown, the class is not abstract, so it is instantiable
				//
				sourceWriter.indent();
				sourceWriter.println("if (className.equalsIgnoreCase(" + className + ".class.getName())) ");
				sourceWriter.indent();
				sourceWriter.println ("return new " + className + " ();");
				sourceWriter.outdent(); sourceWriter.outdent();
				sourceWriter.println();
				
			} catch (Exception e) {
				// the class is not public or not found
				
				// also, if class is abstract, no instantiation is possible (InstantiationException is thrown
				// in try block above)
			}			
		}
		
		sourceWriter.outdent();
		sourceWriter.println("return null;");
		
		sourceWriter.println("}");
		sourceWriter.outdent();
		sourceWriter.println("}");

		// commit generated class
		context.commit(logger, printWriter);
	}

	public String[] findElementClasses () {
		
		String pkgName = "org.gwt.mosaic.xul.client.ui";

		ArrayList<String> classes = new ArrayList<String>();
		String name = new String (pkgName);

		if (!name.startsWith("/")) {
			name = "/" + name;
		}        
		name = name.replace('.','/');

		URL url = Element.class.getResource(name);
		File directory = new File(url.getFile());
		
		if (directory.exists()) {
			// Get the list of the files contained in the package
			String [] files = directory.list();
			for (int i=0;i<files.length;i++) {

				if (files[i].endsWith(".class")) {
					String classname = files[i].substring(0,files[i].length()-6);
					if (!classname.contains("$") && !classes.contains (pkgName + "." + classname)) {
						classes.add (pkgName + "." + classname);
					}
				} else if (files [i].endsWith(".java")) {
					String classname = files[i].substring(0, files[i].length() - 5);
					if (!classname.contains("$") && !classes.contains(pkgName + "." + classname)) {
						classes.add (pkgName + "." + classname);
					}				
				}
			}
		}
		
		return classes.toArray(new String [classes.size()]);
	}

}
